home *** CD-ROM | disk | FTP | other *** search
/ Total Network Tools 2002 / NextStepPublishing-TotalNetworkTools2002-Win95.iso / Archive / Misc Servers / Zope.exe / PRODUCERS.PY < prev    next >
Encoding:
Python Source  |  2000-06-02  |  8.0 KB  |  332 lines

  1. # -*- Mode: Python; tab-width: 4 -*-
  2.  
  3. RCS_ID = '$Id: producers.py,v 1.7 2000/06/02 14:22:48 brian Exp $'
  4.  
  5. import string
  6.  
  7. """
  8. A collection of producers.
  9. Each producer implements a particular feature:  They can be combined
  10. in various ways to get interesting and useful behaviors.
  11.  
  12. For example, you can feed dynamically-produced output into the compressing
  13. producer, then wrap this with the 'chunked' transfer-encoding producer.
  14. """
  15.  
  16. class simple_producer:
  17.     "producer for a string"
  18.     def __init__ (self, data, buffer_size=1024):
  19.         self.data = data
  20.         self.buffer_size = buffer_size
  21.  
  22.     def more (self):
  23.         if len (self.data) > self.buffer_size:
  24.             result = self.data[:self.buffer_size]
  25.             self.data = self.data[self.buffer_size:]
  26.             return result
  27.         else:
  28.             result = self.data
  29.             self.data = ''
  30.             return result
  31.  
  32. class scanning_producer:
  33.     "like simple_producer, but more efficient for large strings"
  34.     def __init__ (self, data, buffer_size=1024):
  35.         self.data = data
  36.         self.buffer_size = buffer_size
  37.         self.pos = 0
  38.  
  39.     def more (self):
  40.         if self.pos < len(self.data):
  41.             lp = self.pos
  42.             rp = min (
  43.                 len(self.data),
  44.                 self.pos + self.buffer_size
  45.                 )
  46.             result = self.data[lp:rp]
  47.             self.pos = self.pos + len(result)
  48.             return result
  49.         else:
  50.             return ''
  51.  
  52. class lines_producer:
  53.     "producer for a list of lines"
  54.  
  55.     def __init__ (self, lines):
  56.         self.lines = lines
  57.  
  58.     def ready (self):
  59.         return len(self.lines)
  60.  
  61.     def more (self):
  62.         if self.lines:
  63.             chunk = self.lines[:50]
  64.             self.lines = self.lines[50:]
  65.             return string.join (chunk, '\r\n') + '\r\n'
  66.         else:
  67.             return ''
  68.  
  69. class buffer_list_producer:
  70.     "producer for a list of buffers"
  71.  
  72.     # i.e., data == string.join (buffers, '')
  73.     
  74.     def __init__ (self, buffers):
  75.  
  76.         self.index = 0
  77.         self.buffers = buffers
  78.  
  79.     def more (self):
  80.         if self.index >= len(self.buffers):
  81.             return ''
  82.         else:
  83.             data = self.buffers[self.index]
  84.             self.index = self.index + 1
  85.             return data
  86.  
  87. class file_producer:
  88.     "producer wrapper for file[-like] objects"
  89.  
  90.     # match http_channel's outgoing buffer size
  91.     out_buffer_size = 1<<16
  92.  
  93.     def __init__ (self, file):
  94.         self.done = 0
  95.         self.file = file
  96.  
  97.     def more (self):
  98.         if self.done:
  99.             return ''
  100.         else:
  101.             data = self.file.read (self.out_buffer_size)
  102.             if not data:
  103.                 self.file.close()
  104.                 del self.file
  105.                 self.done = 1
  106.                 return ''
  107.             else:
  108.                 return data
  109.  
  110. # A simple output producer.  This one does not [yet] have
  111. # the safety feature builtin to the monitor channel:  runaway
  112. # output will not be caught.
  113.  
  114. # don't try to print from within any of the methods
  115. # of this object.
  116.  
  117. class output_producer:
  118.     "Acts like an output file; suitable for capturing sys.stdout"
  119.     def __init__ (self):
  120.         self.data = ''
  121.             
  122.     def write (self, data):
  123.         lines = string.splitfields (data, '\n')
  124.         data = string.join (lines, '\r\n')
  125.         self.data = self.data + data
  126.         
  127.     def writeline (self, line):
  128.         self.data = self.data + line + '\r\n'
  129.         
  130.     def writelines (self, lines):
  131.         self.data = self.data + string.joinfields (
  132.             lines,
  133.             '\r\n'
  134.             ) + '\r\n'
  135.  
  136.     def ready (self):
  137.         return (len (self.data) > 0)
  138.  
  139.     def flush (self):
  140.         pass
  141.  
  142.     def softspace (self, *args):
  143.         pass
  144.  
  145.     def more (self):
  146.         if self.data:
  147.             result = self.data[:512]
  148.             self.data = self.data[512:]
  149.             return result
  150.         else:
  151.             return ''
  152.  
  153. class composite_producer:
  154.     "combine a fifo of producers into one"
  155.     def __init__ (self, producers):
  156.         self.producers = producers
  157.  
  158.     def more (self):
  159.         while len(self.producers):
  160.             p = self.producers.first()
  161.             d = p.more()
  162.             if d:
  163.                 return d
  164.             else:
  165.                 self.producers.pop()
  166.         else:
  167.             return ''
  168.  
  169.  
  170. class globbing_producer:
  171.     """
  172.     'glob' the output from a producer into a particular buffer size.
  173.     helps reduce the number of calls to send().  [this appears to
  174.     gain about 30% performance on requests to a single channel]
  175.     """
  176.  
  177.     def __init__ (self, producer, buffer_size=1<<16):
  178.         self.producer = producer
  179.         self.buffer = ''
  180.         self.buffer_size = buffer_size
  181.  
  182.     def more (self):
  183.         while len(self.buffer) < self.buffer_size:
  184.             data = self.producer.more()
  185.             if data:
  186.                 self.buffer = self.buffer + data
  187.             else:
  188.                 break
  189.         r = self.buffer
  190.         self.buffer = ''
  191.         return r
  192.  
  193.  
  194. class hooked_producer:
  195.     """
  196.     A producer that will call <function> when it empties,.
  197.     with an argument of the number of bytes produced.  Useful
  198.     for logging/instrumentation purposes.
  199.     """
  200.  
  201.     def __init__ (self, producer, function):
  202.         self.producer = producer
  203.         self.function = function
  204.         self.bytes = 0
  205.  
  206.     def more (self):
  207.         if self.producer:
  208.             result = self.producer.more()
  209.             if not result:
  210.                 self.producer = None
  211.                 self.function (self.bytes)
  212.             else:
  213.                 self.bytes = self.bytes + len(result)
  214.             return result
  215.         else:
  216.             return ''
  217.  
  218. # HTTP 1.1 emphasizes that an advertised Content-Length header MUST be
  219. # correct.  In the face of Strange Files, it is conceivable that
  220. # reading a 'file' may produce an amount of data not matching that
  221. # reported by os.stat() [text/binary mode issues, perhaps the file is
  222. # being appended to, etc..]  This makes the chunked encoding a True
  223. # Blessing, and it really ought to be used even with normal files.
  224. # How beautifully it blends with the concept of the producer.
  225.  
  226. class chunked_producer:
  227.     """A producer that implements the 'chunked' transfer coding for HTTP/1.1.
  228.     Here is a sample usage:
  229.         request['Transfer-Encoding'] = 'chunked'
  230.         request.push (
  231.             producers.chunked_producer (your_producer)
  232.             )
  233.         request.done()
  234.     """
  235.  
  236.     def __init__ (self, producer, footers=None):
  237.         self.producer = producer
  238.         self.footers = footers
  239.  
  240.     def more (self):
  241.         if self.producer:
  242.             data = self.producer.more()
  243.             if data:
  244.                 return '%x\r\n%s\r\n' % (len(data), data)
  245.             else:
  246.                 self.producer = None
  247.                 if self.footers:
  248.                     return string.join (
  249.                         ['0'] + self.footers,
  250.                         '\r\n'
  251.                         ) + '\r\n\r\n'
  252.                 else:
  253.                     return '0\r\n\r\n'
  254.         else:
  255.             return ''
  256.  
  257. # Unfortunately this isn't very useful right now (Aug 97), because
  258. # apparently the browsers don't do on-the-fly decompression.  Which
  259. # is sad, because this could _really_ speed things up, especially for
  260. # low-bandwidth clients (i.e., most everyone).
  261.  
  262. try:
  263.     import zlib
  264. except ImportError:
  265.     zlib = None
  266.  
  267. class compressed_producer:
  268.     """
  269.     Compress another producer on-the-fly, using ZLIB
  270.     [Unfortunately, none of the current browsers seem to support this]
  271.     """
  272.  
  273.     # Note: It's not very efficient to have the server repeatedly
  274.     # compressing your outgoing files: compress them ahead of time, or
  275.     # use a compress-once-and-store scheme.  However, if you have low
  276.     # bandwidth and low traffic, this may make more sense than
  277.     # maintaining your source files compressed.
  278.     #
  279.     # Can also be used for compressing dynamically-produced output.
  280.  
  281.     def __init__ (self, producer, level=5):
  282.         self.producer = producer
  283.         self.compressor = zlib.compressobj (level)
  284.  
  285.     def more (self):
  286.         if self.producer:
  287.             cdata = ''
  288.             # feed until we get some output
  289.             while not cdata:
  290.                 data = self.producer.more()
  291.                 if not data:
  292.                     self.producer = None
  293.                     return self.compressor.flush()
  294.                 else:
  295.                     cdata = self.compressor.compress (data)
  296.             return cdata
  297.         else:
  298.             return ''
  299.  
  300. class escaping_producer:
  301.  
  302.     "A producer that escapes a sequence of characters"
  303.     " Common usage: escaping the CRLF.CRLF sequence in SMTP, NNTP, etc..."
  304.  
  305.     def __init__ (self, producer, esc_from='\r\n.', esc_to='\r\n..'):
  306.         self.producer = producer
  307.         self.esc_from = esc_from
  308.         self.esc_to = esc_to
  309.         self.buffer = ''
  310.         from asynchat import find_prefix_at_end
  311.         self.find_prefix_at_end = find_prefix_at_end
  312.  
  313.     def more (self):
  314.         esc_from = self.esc_from
  315.         esc_to   = self.esc_to
  316.  
  317.         buffer = self.buffer + self.producer.more()
  318.  
  319.         if buffer:
  320.             buffer = string.replace (buffer, esc_from, esc_to)
  321.             i = self.find_prefix_at_end (buffer, esc_from)
  322.             if i:
  323.                 # we found a prefix
  324.                 self.buffer = buffer[-i:]
  325.                 return buffer[:-i]
  326.             else:
  327.                 # no prefix, return it all
  328.                 self.buffer = ''
  329.                 return buffer
  330.         else:
  331.             return buffer
  332.